package midas.mw.crt;

import java.io.*;
import java.net.*;
import java.util.StringTokenizer;

import midas.mw.crt.interfaces.Crt;
import midas.mw.crt.common.CBMessage;

import midas.mw.cfg.CfgLogging;
import midas.mw.ckb.interfaces.Ckb;
import midas.mw.ckb.interfaces.ConstructedClass;
import midas.mw.ckb.interfaces.DomainClass;
import midas.mw.ckb.interfaces.DomainInstance;
import midas.mw.ckb.interfaces.DomainProperty;
import midas.mw.ckb.interfaces.NotInModelException;
import midas.mw.ckb.interfaces.QueryAtom;

public class CrtServer implements Runnable {

	private static CbrImpl cbrInterface = null;
	private int tcpPort;
	private String tcpIP;

	public CrtServer(CbrImpl cbrInp, int port, String ip)
	{
		cbrInterface = cbrInp;
		tcpPort = port;
		tcpIP = ip;
	}

	public static CbrImpl getCbrHandle()
	{
		return cbrInterface;
	}

	public void run()
	{
		ServerSocket serverSocket = null;
		Socket clientSocket = null;

		try {
			// Create the server socket

			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_INFORMAL, "starting CrtServer on port " + tcpPort);

			serverSocket = new ServerSocket(tcpPort, 1,Inet4Address.getByName(tcpIP));

			while (true) {
				// Wait for a connection
				clientSocket = serverSocket.accept();
				CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "connection accepted");

				//Service the connection
				ServiceClient(clientSocket);

				CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "closing connection");
				clientSocket.close();
				CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "connection closed");
			}
		}
		catch (IOException ioe) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_ERROR, "run(): IOException " + ioe);
		}
	}

	private static StringBuffer getSuperClasses(String className)
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");
		ConstructedClass cc = ckb.reconstructConstructedClass(className.getBytes());

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getSuperClasses()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className = " + className);

		QueryAtom[] d_arr = cc.getSuperConcepts(QueryAtom.CLASS_ATOM_TYPES);

		output.append(d_arr.length);
		output.append("\r\n");

		for (int i = 0; i < d_arr.length; i++) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + d_arr[i].toString());
			output.append(d_arr[i].toString());
			output.append("\r\n");
		}

		return output;
	}

	private static StringBuffer getSubClasses(String className)
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");
		ConstructedClass cc = ckb.reconstructConstructedClass(className.getBytes());

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getSubClasses()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className = " + className);

		QueryAtom[] d_arr = cc.getSubConcepts(QueryAtom.CLASS_ATOM_TYPES);

		output.append(d_arr.length);
		output.append("\r\n");

		for (int i = 0; i < d_arr.length; i++) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + d_arr[i].toString());
			output.append(d_arr[i].getQueryAtomName());
			output.append("\r\n");
		}

		return output;
	}

	private static StringBuffer getElementaryConcepts(String className)
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getElementaryConcepts()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className = " + className);

		String[] s_arr = ckb.parseAddressIntoElementaryConcepts(className.getBytes());
		
		output.append(s_arr.length);
		output.append("\r\n");

		for (int i = 0; i < s_arr.length; i++) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + s_arr[i]);
			output.append(s_arr[i]);
			output.append("\r\n");
		}

		return output;
	}

	private static StringBuffer getSuperConcepts(String className, Integer level)
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getSuperConcepts()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className = " + className);

		String[] s_arr = ckb.getSuperConcepts(className, level.intValue());

		output.append(s_arr.length);
		output.append("\r\n");

		for (int i = 0; i < s_arr.length; i++) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + s_arr[i]);
			output.append(s_arr[i]);
			output.append("\r\n");
		}

		return output;
	}

	private static StringBuffer getSubConcepts(String className)
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getSubConcepts()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className = " + className);

		String[] s_arr = ckb.getSubConcepts(className);
		
		output.append(s_arr.length);
		output.append("\r\n");

		for (int i = 0; i < s_arr.length; i++) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + s_arr[i]);
			output.append(s_arr[i]);
			output.append("\r\n");
		}

		return output;
	}

	private static StringBuffer getCommonSuperClasses(String className1, String className2)
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");
		String[] d_arr = ckb.getCommonSuperConcepts(className1, className2);

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getCommonSuperClasses()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className1 = " + className1);
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className2 = " + className2);

		output.append(d_arr.length);
		output.append("\r\n");

		for (int i = 0; i < d_arr.length; i++) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + d_arr[i].toString());
			output.append(d_arr[i]);
			output.append("\r\n");
		}

		return output;
	}

	private static StringBuffer getClosestCommonSuperClass(String className1, String className2)
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");
		String d = ckb.getClosestCommonSuperConcept(className1, className2);

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getClosestCommonSuperClass()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className1 = " + className1);
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " className2 = " + className2);

		output.append("1\r\n");

		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + d.toString());
		output.append(d.toString());
		output.append("\r\n");

		return output;
	}

	private static StringBuffer checkCBA(String cba)
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "checkCBA()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " CBA = " + cba);

		boolean check = false;

		ConstructedClass adde = ckb.reconstructConstructedClass(cba);
		check = ckb.checkAddress(adde.getBinaryRepresentation());

		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + check);
		output.append(check);
		output.append("\r\n");

		return output;
	}

	private static StringBuffer getCtxDesc()
	{
		StringBuffer output = new StringBuffer();
		Ckb ckb = (Ckb)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.ckb.CkbImpl");

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getCtxDesc()");

		QueryAtom [] qArr = ckb.getContextDescription();

		String s = "";
		for (int i = 0; i < qArr.length; i++) {
			s += qArr[i].getQueryAtomName();
			if (i + 1 != qArr.length)
				s += ",";
		}

		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " " + s);
		output.append(s);
		output.append("\r\n");

		return output;
	}

	private static StringBuffer getNodeIP(String NodeID)
	{
		StringBuffer output = new StringBuffer();
		String full_addr = null, ip_only = null;

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "getNodeIP()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " NodeID = " + NodeID);

		/* TODO: check why Crt doesn't contain getNodeIdentifierSolver() */
//		Crt crt = (Crt)getCbrHandle().getMiddlewareInterface().getRunningComponent("midas.mw.crt.CrtImpl");
//		full_addr = ((CrtImpl)crt).getNodeIdentifierSolver().getIPAddress(NodeID);

		/* TODO: handle ip:port in CBR */
		if (full_addr != null) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "full_addr: " + full_addr);
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "ip_only: " + ip_only);
			int i = full_addr.indexOf(":");
			ip_only = full_addr.substring(0, i);
			output.append(ip_only);
		} else {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " no IP");
		}
		output.append("\r\n");

		return output;
	}

	public static void ServiceClient(Socket client) throws IOException
	{
		DataInputStream inbound = null;
		DataOutputStream outbound = null;

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "ServiceClient()");

		try {
			// Acquire the streams for IO
			inbound = new DataInputStream( client.getInputStream());
			outbound = new DataOutputStream( client.getOutputStream());

				// Format the output
				StringBuffer buffer = null; // = PrepareOutput();

				String inputLine = inbound.readLine();
				CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, inputLine);

				if (inputLine == null) {
					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "empty request: " + client);
					return; // empty request
				}

				StringTokenizer st = new StringTokenizer(inputLine);
				String reqString = new String(st.nextToken());

				if (reqString.equals("GETSUPERCLASSES")) {
					String concept = st.nextToken();
					buffer = getSuperClasses(concept);
				} else if (reqString.equals("GETSUBCLASSES")) {
					String concept = st.nextToken();
					buffer = getSubClasses(concept);
				} else if (reqString.equals("GETELEMENTARYCONCEPTS")) {
					String concept = st.nextToken();
					buffer = getElementaryConcepts(concept);
				} else if (reqString.equals("GETSUPERCONCEPTS")) {
					String concept = st.nextToken();
					String level_str = st.nextToken();
					Integer level = new Integer(level_str);
					buffer = getSuperConcepts(concept, level);
				} else if (reqString.equals("GETSUBCONCEPTS")) {
					String concept = st.nextToken();
					buffer = getSubConcepts(concept);
				} else if (reqString.equals("GETCOMMONSUPERCLASSES")) {
					String concept1 = st.nextToken();
					String concept2 = st.nextToken();
					buffer = getCommonSuperClasses(concept1, concept2);
				} else if (reqString.equals("GETCLOSESETCOMMONSUPERCLASS")) {
					String concept1 = st.nextToken();
					String concept2 = st.nextToken();
					buffer = getClosestCommonSuperClass(concept1, concept2);
				} else if (reqString.equals("CHECKCBA")) {
					String cba = st.nextToken();
					buffer = checkCBA(cba);
				} else if (reqString.equals("GETCTXDESC")) {
					String id = st.nextToken();
					CbrImpl cbr = getCbrHandle();
					cbr.setLocalNodeID(Integer.parseInt(id));
					buffer = getCtxDesc();
				} else if (reqString.equals("GETNODEIP")) {
					String NodeID = st.nextToken();
					buffer = getNodeIP(NodeID);
				} else if (reqString.equals("GETMSG")) {
					buffer = new StringBuffer();
					CbrImpl crt = getCbrHandle();

					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "GETMSG");

//					if (crt.checkCBMessage()) {
						CBMessage msg = crt.getCBMessage();
						String destination = msg.getDestination();
						Integer flags = new Integer(msg.getFlags());

						CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " destination: " + destination);
						CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " flags: " + flags.toString());

						buffer.append(destination);
						buffer.append("\r\n");
						buffer.append(flags.toString());
						buffer.append("\r\n");

						outbound.writeBytes("CBR/0.1 202\r\n");

						byte b[] = msg.getPayload();

						CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " b: " + b);

						outbound.writeShort(buffer.length() + 2 + b.length);
						outbound.writeBytes("\r\n");
						outbound.writeBytes(buffer.toString());

						int i = 0;
						do {
							outbound.writeByte(b[i++]);
						} while (i < b.length);

						outbound.writeBytes("\r\n");

						// do not use generic send path
						return;
//					}
				} else if (reqString.equals("PUTMSG")) {
					buffer = new StringBuffer();
					int id = Integer.parseInt(st.nextToken());
					int size = Integer.parseInt(st.nextToken());
					byte[] b = new byte[size];

					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "PUTMSG");

					int i = 0;
					do {
						b[i++] = inbound.readByte();
					} while (i < size);

					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " size: " + size);
					String str = new String(b);
					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, " b: " + str);

					if (id == -2)
						((CbrImpl)getCbrHandle()).incomingMessage(b);
					else {
						CbrImpl cbr = (CbrImpl)getCbrHandle(); 
						Crt crt = (Crt)cbr.getMiddlewareInterface().getRunningComponent("midas.mw.crt.CrtImpl");
	
						if (((CrtImpl)crt).getConstants().NODE_TYPE != 0)
							cbr.sendMessageToCentralNode(b);
						else {
							CBMessage msg = new CBMessage(Integer.toString(id), b, 0);

							cbr.sendMessageToNodes(msg);
						}
					}
					} else {
					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_ERROR, "unknown request: " + client);
					return;
				}

				// always send header, format is following:
				//
				// CBR/0.1 202\r\n<lenght>\r\n<data>
				//
				// <length> is 2 bytes integer number (may be 0)
				// <data> data to send (if any)

				outbound.writeBytes("CBR/0.1 202\r\n");
				//System.out.println(buffer.length());
				if (buffer.length() == 2)
					outbound.writeShort(0);
				else
					outbound.writeShort(buffer.length());
				outbound.writeBytes("\r\n");
				if (buffer.length() > 2)
					outbound.writeBytes(buffer.toString());
		}
		finally {
			// Clean up
			try{
				if (inbound!=null) inbound.close();
				if (outbound!=null) outbound.close();
			}catch(Exception e){}
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrServer", CfgLogging.SEV_DEBUG, "cleaning up connection: " + client);
		}
	}
}
